package pdh

import (
	"errors"
	"time"
	"unsafe"

	"gitee.com/general252/pdh/core"
)

// Query provides wrappers around Windows performance counters API for easy usage in GO
type Query interface {
	Open() error
	Close() error

	// EnumObject 可用计数器["Process", "Processor", "Processor Information", ...]
	EnumObject() (objects []string, err error)
	// EnumObjectItems 计数器对象的实例列表["_Total", "0", ...]
	EnumObjectItems(objectName string) (instances []string, err error)

	// AddCounter 添加计数器 "\\Processor Information(_Total)\\% Processor Utility"
	// https://learn.microsoft.com/zh-cn/windows/win32/perfctrs/specifying-a-counter-path?source=recommendations
	AddCounter(counterPath string) (core.PDH_HCOUNTER, error)
	AddEnglishCounter(counterPath string) (core.PDH_HCOUNTER, error)
	RemoveCounter(counter core.PDH_HCOUNTER) error

	// CollectData 采集数据
	CollectData() error
	// GetFormattedCounterValueDouble 获取double数据
	GetFormattedCounterValueDouble(hCounter core.PDH_HCOUNTER) (float64, error)
	// GetFormattedCounterArrayDouble 获取[]double数据
	GetFormattedCounterArrayDouble(hCounter core.PDH_HCOUNTER) ([]CounterValue, error)

	CollectDataWithTime() (time.Time, error)

	GetCounterPath(counterHandle core.PDH_HCOUNTER) (string, error)
	ExpandWildCardPath(counterPath string) ([]string, error)

	IsVistaOrNewer() bool
}

// queryImpl is implementation of Query interface, which calls phd.dll functions
type queryImpl struct {
	query core.PDH_HQUERY
}

func NewQuery() Query {
	return &queryImpl{}
}

// Open creates a new counterPath that is used to manage the collection of performance data.
// It returns counterPath handle used for subsequent calls for adding counters and querying data
func (m *queryImpl) Open() error {
	if m.query != 0 {
		err := m.Close()
		if err != nil {
			return err
		}
	}
	var handle core.PDH_HQUERY

	if ret := core.PdhOpenQuery(0, 0, &handle); ret != core.ERROR_SUCCESS {
		return newPdhError(ret)
	}
	m.query = handle
	return nil
}

// Close closes the counterPath, releases associated counter handles and frees resources
func (m *queryImpl) Close() error {
	if m.query == 0 {
		return errors.New("uninitialised query")
	}

	if ret := core.PdhCloseQuery(m.query); ret != core.ERROR_SUCCESS {
		return newPdhError(ret)
	}
	m.query = 0
	return nil
}

func (m *queryImpl) EnumObject() (objects []string, err error) {
	return core.PdhEnumObject()
}

func (m *queryImpl) EnumObjectItems(objectName string) (instances []string, err error) {
	return core.PdhEnumObjectItems(objectName)
}

func (m *queryImpl) AddCounter(counterPath string) (core.PDH_HCOUNTER, error) {
	var counterHandle core.PDH_HCOUNTER
	if m.query == 0 {
		return 0, errors.New("uninitialised query")
	}

	if ret := core.PdhAddCounter(m.query, counterPath, 0, &counterHandle); ret != core.ERROR_SUCCESS {
		return 0, newPdhError(ret)
	}
	return counterHandle, nil
}

func (m *queryImpl) AddEnglishCounter(counterPath string) (core.PDH_HCOUNTER, error) {
	var counterHandle core.PDH_HCOUNTER
	if m.query == 0 {
		return 0, errors.New("uninitialised query")
	}
	if ret := core.PdhAddEnglishCounter(m.query, counterPath, 0, &counterHandle); ret != core.ERROR_SUCCESS {
		return 0, newPdhError(ret)
	}
	return counterHandle, nil
}

func (m *queryImpl) RemoveCounter(counter core.PDH_HCOUNTER) error {
	if ret := core.PdhRemoveCounter(counter); ret != core.ERROR_SUCCESS {
		return newPdhError(ret)
	}
	return nil
}

func (m *queryImpl) CollectData() error {
	var ret uint32
	if m.query == 0 {
		return errors.New("uninitialised query")
	}

	if ret = core.PdhCollectQueryData(m.query); ret != core.ERROR_SUCCESS {
		return newPdhError(ret)
	}
	return nil
}

// GetFormattedCounterValueDouble computes a displayable value for the specified counter
func (m *queryImpl) GetFormattedCounterValueDouble(hCounter core.PDH_HCOUNTER) (float64, error) {
	var counterType uint32
	var value core.PDH_FMT_COUNTERVALUE_DOUBLE
	var ret uint32

	if ret = core.PdhGetFormattedCounterValueDouble(hCounter, &counterType, &value); ret == core.ERROR_SUCCESS {
		if value.CStatus == core.PDH_CSTATUS_VALID_DATA || value.CStatus == core.PDH_CSTATUS_NEW_DATA {
			return value.DoubleValue, nil
		} else {
			return 0, newPdhError(value.CStatus)
		}
	} else {
		return 0, newPdhError(ret)
	}
}

func (m *queryImpl) GetFormattedCounterArrayDouble(hCounter core.PDH_HCOUNTER) ([]CounterValue, error) {
	var buffSize uint32
	var itemCount uint32
	var ret uint32

	if ret = core.PdhGetFormattedCounterArrayDouble(hCounter, &buffSize, &itemCount, nil); ret == core.PDH_MORE_DATA {
		buff := make([]byte, buffSize)

		if ret = core.PdhGetFormattedCounterArrayDouble(hCounter, &buffSize, &itemCount, &buff[0]); ret == core.ERROR_SUCCESS {
			items := (*[1 << 20]core.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE)(unsafe.Pointer(&buff[0]))[:itemCount]
			values := make([]CounterValue, 0, itemCount)
			for _, item := range items {
				if item.FmtValue.CStatus == core.PDH_CSTATUS_VALID_DATA || item.FmtValue.CStatus == core.PDH_CSTATUS_NEW_DATA {
					val := CounterValue{core.UTF16PtrToString(item.SzName), item.FmtValue.DoubleValue}
					values = append(values, val)
				}
			}
			return values, nil
		}
	}
	return nil, newPdhError(ret)
}

func (m *queryImpl) CollectDataWithTime() (time.Time, error) {
	if m.query == 0 {
		return time.Now(), errors.New("uninitialised query")
	}

	ret, mtime := core.PdhCollectQueryDataWithTime(m.query)
	if ret != core.ERROR_SUCCESS {
		return time.Now(), newPdhError(ret)
	}
	return mtime, nil
}

// GetCounterPath return counter information for given handle
func (m *queryImpl) GetCounterPath(counterHandle core.PDH_HCOUNTER) (string, error) {
	var bufSize uint32
	var buff []byte
	var ret uint32
	if ret = core.PdhGetCounterInfo(counterHandle, 0, &bufSize, nil); ret == core.PDH_MORE_DATA {
		buff = make([]byte, bufSize)
		bufSize = uint32(len(buff))
		if ret = core.PdhGetCounterInfo(counterHandle, 0, &bufSize, &buff[0]); ret == core.ERROR_SUCCESS {
			ci := (*core.PDH_COUNTER_INFO)(unsafe.Pointer(&buff[0]))
			return core.UTF16PtrToString(ci.SzFullPath), nil
		}
	}
	return "", newPdhError(ret)
}

// ExpandWildCardPath  examines local computer and returns those counter paths that match the given counter path which contains wildcard characters.
func (m *queryImpl) ExpandWildCardPath(counterPath string) ([]string, error) {
	var bufSize uint32
	var buff []uint16
	var ret uint32

	if ret = core.PdhExpandWildCardPath(counterPath, nil, &bufSize); ret == core.PDH_MORE_DATA {
		buff = make([]uint16, bufSize)
		bufSize = uint32(len(buff))
		ret = core.PdhExpandWildCardPath(counterPath, &buff[0], &bufSize)
		if ret == core.ERROR_SUCCESS {
			list := core.UTF16ToStringArray(buff)
			return list, nil
		}
	}
	return nil, newPdhError(ret)
}

func (m *queryImpl) IsVistaOrNewer() bool {
	return core.PdhAddEnglishCounterSupported()
}

// CounterValue query is abstraction for PDH_FMT_COUNTERVALUE_ITEM_DOUBLE
type CounterValue struct {
	InstanceName string
	Value        float64
}

// PdhError represents error returned from Performance Counters API
type PdhError struct {
	ErrorCode uint32
	errorText string
}

func (m *PdhError) Error() string {
	return m.errorText
}

func newPdhError(code uint32) error {
	return &PdhError{
		ErrorCode: code,
		errorText: core.PdhFormatError(code),
	}
}
